1
|
|
|
var jsrsasign = require('jsrsasign'); |
2
|
|
|
var assert = require('assert'); |
3
|
|
|
var bip70 = require('../main.js'); |
4
|
|
|
var PaymentRequest = bip70.ProtoBuf.PaymentRequest; |
5
|
|
|
var ChainPathValidator = bip70.X509.ChainPathValidator; |
6
|
|
|
var ChainPathBuilder = bip70.X509.ChainPathBuilder; |
7
|
|
|
var certfile = require("./certfile"); |
8
|
|
|
|
9
|
|
|
|
10
|
|
|
function addDays(date, days) { |
11
|
|
|
var result = new Date(date); |
12
|
|
|
result.setDate(result.getDate() + days); |
13
|
|
|
return result; |
14
|
|
|
} |
15
|
|
|
|
16
|
|
|
function subDays(date, days) { |
17
|
|
|
var result = new Date(date); |
18
|
|
|
result.setDate(result.getDate() - days); |
19
|
|
|
return result; |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
function certFromEncoding(data, encoding) { |
23
|
|
|
var b = Buffer.from(data, encoding); |
24
|
|
|
var cert = new jsrsasign.X509(); |
25
|
|
|
cert.readCertHex(b.toString('hex')); |
26
|
|
|
return cert; |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
describe("GetSignatureAlgorithm", function() { |
30
|
|
|
var entityCert = certFromEncoding(certfile.test_cert.entityCertificate, "base64"); |
31
|
|
|
|
32
|
|
|
it("Deals with RSA public keys", function(cb) { |
33
|
|
|
var sha256 = bip70.X509.GetSignatureAlgorithm(entityCert, bip70.X509.PKIType.X509_SHA256); |
34
|
|
|
assert.equal(sha256, "SHA256withRSA"); |
35
|
|
|
|
36
|
|
|
var sha1 = bip70.X509.GetSignatureAlgorithm(entityCert, bip70.X509.PKIType.X509_SHA1); |
37
|
|
|
assert.equal(sha1, "SHA1withRSA"); |
38
|
|
|
cb(); |
39
|
|
|
}); |
40
|
|
|
|
41
|
|
|
it("Deals with ECDSA public keys (mocked)", function(cb) { |
42
|
|
|
var mockKey = {}; |
43
|
|
|
mockKey.getPublicKey = function() { |
44
|
|
|
return { |
45
|
|
|
type: "ECDSA" |
46
|
|
|
}; |
47
|
|
|
}; |
48
|
|
|
|
49
|
|
|
var sha256 = bip70.X509.GetSignatureAlgorithm(mockKey, bip70.X509.PKIType.X509_SHA256); |
50
|
|
|
assert.equal(sha256, "SHA256withECDSA"); |
51
|
|
|
|
52
|
|
|
var sha1 = bip70.X509.GetSignatureAlgorithm(mockKey, bip70.X509.PKIType.X509_SHA1); |
53
|
|
|
assert.equal(sha1, "SHA1withECDSA"); |
54
|
|
|
cb(); |
55
|
|
|
}); |
56
|
|
|
|
57
|
|
|
it("Rejects unknown PKI types", function(cb) { |
58
|
|
|
var mockKey = {}; |
59
|
|
|
mockKey.getPublicKey = function() { |
60
|
|
|
return { |
61
|
|
|
type: "ECDSA" |
62
|
|
|
}; |
63
|
|
|
}; |
64
|
|
|
|
65
|
|
|
assert.throws(function() { |
66
|
|
|
bip70.X509.GetSignatureAlgorithm(mockKey, "unknown"); |
67
|
|
|
}, "Unknown PKI type or no signature algorithm specified."); |
68
|
|
|
|
69
|
|
|
assert.throws(function() { |
70
|
|
|
bip70.X509.GetSignatureAlgorithm(mockKey, bip70.X509.PKIType.NONE); |
71
|
|
|
}, "Unknown PKI type or no signature algorithm specified."); |
72
|
|
|
|
73
|
|
|
cb(); |
74
|
|
|
}); |
75
|
|
|
|
76
|
|
|
it("Rejects unknown public key type", function(cb) { |
77
|
|
|
var mockKey = {}; |
78
|
|
|
mockKey.getPublicKey = function() { |
79
|
|
|
return { |
80
|
|
|
type: "wut" |
81
|
|
|
}; |
82
|
|
|
}; |
83
|
|
|
|
84
|
|
|
assert.throws(function() { |
85
|
|
|
bip70.X509.GetSignatureAlgorithm(mockKey, "unknown"); |
86
|
|
|
}, "Unknown public key type"); |
87
|
|
|
|
88
|
|
|
cb(); |
89
|
|
|
}); |
90
|
|
|
}); |
91
|
|
|
|
92
|
|
|
describe('ChainPathBuilder', function() { |
93
|
|
|
var fixture = certfile.test_cert; |
94
|
|
|
var entityCert = certFromEncoding(fixture.entityCertificate, "base64"); |
95
|
|
|
var rootCert = certFromEncoding(fixture.rootCertificate, "base64"); |
96
|
|
|
var intermediates = fixture.intermediates.map(function(base64) { |
97
|
|
|
return certFromEncoding(base64, "base64"); |
98
|
|
|
}); |
99
|
|
|
var chainValidTime = fixture.chainValidTime; |
100
|
|
|
|
101
|
|
|
it('builds a valid certificate chain', function(cb) { |
102
|
|
|
var numCerts = 2 + intermediates.length; |
103
|
|
|
var builder = new ChainPathBuilder([rootCert]); |
104
|
|
|
var path = builder.shortestPathToTarget(entityCert, intermediates); |
105
|
|
|
|
106
|
|
|
assert.equal(path.length, numCerts, "expecting " + numCerts + " certificates in total for path"); |
107
|
|
|
|
108
|
|
|
var validator = new ChainPathValidator({ |
109
|
|
|
currentTime: chainValidTime |
110
|
|
|
}, path); |
111
|
|
|
|
112
|
|
|
assert.doesNotThrow(function() { |
113
|
|
|
validator.validate(); |
114
|
|
|
}, 'no errors expected during validation'); |
115
|
|
|
|
116
|
|
|
cb(); |
117
|
|
|
}); |
118
|
|
|
|
119
|
|
|
function testCertificateValidity(now) { |
120
|
|
|
var builder = new ChainPathBuilder([rootCert]); |
121
|
|
|
var path = builder.shortestPathToTarget(entityCert, intermediates); |
122
|
|
|
assert.equal(path.length, 3, "expecting 3 certificates in total for path"); |
123
|
|
|
|
124
|
|
|
var validator = new ChainPathValidator({ |
125
|
|
|
currentTime: now |
126
|
|
|
}, path); |
127
|
|
|
|
128
|
|
|
var err; |
129
|
|
|
try { |
130
|
|
|
validator.validate(); |
131
|
|
|
} catch (e) { |
132
|
|
|
err = e; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
assert.ok(typeof err === "object"); |
136
|
|
|
assert.equal(err.message, "Certificate is not valid"); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
[rootCert].concat(intermediates).concat(entityCert).map(function(cert, i) { |
140
|
|
|
it('rejects if `now` is after #' + i + ' certs `notAfter`', function(cb) { |
141
|
|
|
// this timestamp conflicts with the root certificates notBefore |
142
|
|
|
var now = jsrsasign.zulutodate(cert.getNotAfter()); |
143
|
|
|
now = addDays(now, 2); |
144
|
|
|
testCertificateValidity(now); |
145
|
|
|
cb(); |
146
|
|
|
}); |
147
|
|
|
|
148
|
|
|
it('rejects if `now` is before #' + i + ' certs `notBefore`', function(cb) { |
149
|
|
|
// this timestamp conflicts with the root certificates notBefore |
150
|
|
|
var now = jsrsasign.zulutodate(rootCert.getNotBefore()); |
151
|
|
|
now = subDays(now, 2); |
152
|
|
|
testCertificateValidity(now); |
153
|
|
|
cb(); |
154
|
|
|
}); |
155
|
|
|
}); |
156
|
|
|
|
157
|
|
|
function testNoCertificationPath(trusted, intermediate, end) { |
158
|
|
|
var err; |
159
|
|
|
try { |
160
|
|
|
var builder = new ChainPathBuilder(trusted); |
161
|
|
|
builder.shortestPathToTarget(end, intermediate); |
162
|
|
|
} catch (e) { |
163
|
|
|
err = e; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
assert.equal(typeof err, "object"); |
167
|
|
|
assert.equal(err.message, "No certificate paths found"); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
it('errors if intermediate certificate is missing', function(cb) { |
171
|
|
|
testNoCertificationPath([rootCert], [], entityCert); |
172
|
|
|
cb(); |
173
|
|
|
}); |
174
|
|
|
|
175
|
|
|
it('errors if root certificate is missing', function(cb) { |
176
|
|
|
testNoCertificationPath([], intermediates, entityCert); |
177
|
|
|
cb(); |
178
|
|
|
}); |
179
|
|
|
}); |
180
|
|
|
|
181
|
|
|
describe("RequestValidator", function() { |
182
|
|
|
describe("validateSignature", function() { |
183
|
|
|
var i = 0; |
184
|
|
|
certfile.test_cert.requests.map(function(request) { |
185
|
|
|
it("works with a test fixture " + i, function(cb) { |
186
|
|
|
var time = request.time; |
187
|
|
|
var request64 = request.request; |
188
|
|
|
var req = PaymentRequest.decode(Buffer.from(request64, 'base64')); |
189
|
|
|
var root = certFromEncoding(certfile.test_cert.rootCertificate, "base64"); |
190
|
|
|
var intermediates = certfile.test_cert.intermediates.map(function(cert) { |
191
|
|
|
return certFromEncoding(cert, "base64"); |
192
|
|
|
}); |
193
|
|
|
var entityCert = certFromEncoding(certfile.test_cert.entityCertificate, "base64"); |
194
|
|
|
var validator = new bip70.X509.RequestValidator({ |
195
|
|
|
trustStore: [root], |
196
|
|
|
currentTime: time |
197
|
|
|
}); |
198
|
|
|
|
199
|
|
|
assert.doesNotThrow(function() { |
200
|
|
|
validator.validateCertificateChain(entityCert, intermediates); |
201
|
|
|
}, "should validate certificate chain"); |
202
|
|
|
|
203
|
|
|
assert.doesNotThrow(function() { |
204
|
|
|
validator.validateSignature(req, entityCert); |
205
|
|
|
}, "should validate request signature"); |
206
|
|
|
|
207
|
|
|
var path = []; |
208
|
|
|
assert.doesNotThrow(function() { |
209
|
|
|
path = validator.verifyX509Details(req); |
210
|
|
|
}, "full validation should succeed"); |
211
|
|
|
|
212
|
|
|
assert.equal(path.length, 1 + intermediates.length + 1); |
213
|
|
|
cb(); |
214
|
|
|
}); |
215
|
|
|
i++; |
216
|
|
|
}); |
217
|
|
|
|
218
|
|
|
var request = certfile.test_cert.requests[0]; |
219
|
|
|
it("rejects an invalid signature", function(cb) { |
220
|
|
|
var time = request.time; |
221
|
|
|
var request64 = request.request; |
222
|
|
|
var req = PaymentRequest.decode(Buffer.from(request64, 'base64')); |
223
|
|
|
var root = certFromEncoding(certfile.test_cert.rootCertificate, "base64"); |
224
|
|
|
var entityCert = certFromEncoding(certfile.test_cert.entityCertificate, "base64"); |
225
|
|
|
var validator = new bip70.X509.RequestValidator({ |
226
|
|
|
trustStore: [root], |
227
|
|
|
currentTime: time |
228
|
|
|
}); |
229
|
|
|
|
230
|
|
|
req.signature = Buffer.from("dZmjw+Tg7ssFmBF3gqbHvyImTEZ6ffMYMBTAFiJs0RnpY9bPCzEILbCX6rBeagffaShqmkyn0iU3+h509Ul8rtPbR+C4c26uFJNLMXWbq7QiiIbpwCaJjtQFXipm7bgVlv+swrMTVu/K+atAsY8INUyuE/CrV53fN7P9gKFqlmlMB2MdrN/oFCx2dDWooXIjvl11hJDkae+r3bC+YCMBfe3MFCDpmF/c3+0xkFrw2R7cZLdUu+kBF3iHL0ezslxKJLtYMb1cuc5DWiGbVOZqu/+Gt3Pul3DS7Tk8QNx7ou1As0EiGWc+BKxUm63lNS/JlIUwvx6A+q0nnu7WDA28Hg==", "base64"); |
231
|
|
|
assert.ok(false === validator.validateSignature(req, entityCert)); |
232
|
|
|
|
233
|
|
|
assert.throws(function() { |
234
|
|
|
validator.verifyX509Details(req); |
235
|
|
|
}, "Invalid signature on request"); |
236
|
|
|
|
237
|
|
|
cb(); |
238
|
|
|
}); |
239
|
|
|
}); |
240
|
|
|
|
241
|
|
|
}); |
242
|
|
|
|